/*
 * Copyright (C) 2024 Huawei Device Co., Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
import deviceInfo from '@ohos.deviceInfo';
import process from '@ohos.process';

class Log {
  private static TAG: string = "mars.xlog.log";

  public static LEVEL_VERBOSE: number = 0;
  public static LEVEL_DEBUG: number = 1;
  public static LEVEL_INFO: number = 2;
  public static LEVEL_WARNING: number = 3;
  public static LEVEL_ERROR: number = 4;
  public static LEVEL_FATAL: number = 5;
  public static LEVEL_NONE: number = 6;

  // defaults to LEVEL_NONE
  private static level: number = Log.LEVEL_NONE;
  // public static toastSupportContext = ability_featureAbility.getContext();

  private static debugLog = new class implements Log.LogImp {
    public logV(logInstancePtr: number, tag: string, filename: string, funcname: string,
         line: number, pid: number, tid: number, maintid: number, log: string): void {
      if (Log.level <= Log.LEVEL_VERBOSE) {
        console.log(log);
      }
    }

    public logI(logInstancePtr: number, tag: string, filename: string, funcname: string,
         line: number, pid: number, tid: number, maintid: number, log: string): void {
      if (Log.level <= Log.LEVEL_INFO) {
        console.info(log);
      }
    }

    public logD(logInstancePtr: number, tag: string, filename: string, funcname: string,
         line: number, pid: number, tid: number, maintid: number, log: string): void {
      if (Log.level <= Log.LEVEL_DEBUG) {
        console.debug(log);
      }
    }

    public logW(logInstancePtr: number, tag: string, filename: string, funcname: string,
         line: number, pid: number, tid: number, maintid: number, log: string): void {
      if (Log.level <= Log.LEVEL_WARNING) {
        console.warn(log);
      }

    }

    public logE(logInstancePtr: number, tag: string, filename: string, funcname: string,
         line: number, pid: number, tid: number, maintid: number, log: string): void {
      if (Log.level <= Log.LEVEL_ERROR) {
        console.error(log);
      }
    }

    public logF(logInstancePtr: number, tag: string, filename: string, funcname: string,
         line: number, pid: number, tid: number, maintid: number, log: string): void {
      if (Log.level > Log.LEVEL_FATAL) {
        return;
      }
      console.error(log);
      // if (Log.toastSupportContext != null) {
      //   const workerInstance = new worker.Worker("workers/worker.js");
      //   workerInstance.postMessage("hello world");
//        workerInstance.postMessage(prompt.showToast({message: log, duration: 2000}));
//       }
    }

    public getLogLevel(logInstancePtr: number): number {
      console.log("mock: 006 getLogLevel"+Log.level)
      return Log.level;
    }

    public setAppenderMode(logInstancePtr: number, mode: number): void {

    }

    public openLogInstance(level: number, mode: number, cacheDir: string, logDir: string,
      nameprefix: string, cacheDays: number): number {
      return 0;
    }

    public getXlogInstance(nameprefix: string): number {
      return 0;
    }

    public releaseXlogInstance(nameprefix: string): void {

    }

    public appenderOpen(level: number, mode: number, cacheDir: string, logDir: string, nameprefix: string, cacheDays: number): void {

    }

    public appenderClose(): void {

    }

    public appenderFlush(logInstancePtr: number, isSync: boolean): void {
    }

    public setConsoleLogOpen(logInstancePtr: number, isOpen: boolean): void {

    }

    public setMaxAliveTime(logInstancePtr: number, aliveSeconds: number): void {

    }

    public setMaxFileSize(logInstancePtr: number, aliveSeconds: number): void {

    }
  };

  private static logImp: Log.LogImp = Log.debugLog;

  public static setLogImp(imp: Log.LogImp): void {
    Log.logImp = imp;
  }

  public static getImpl(): Log.LogImp {
    return Log.logImp;
  }

  public static appenderOpen(level: number, mode: number, cacheDir: string,
                             logDir: string, nameprefix: string, cacheDays: number): void {
    if (Log.logImp != null) {
      Log.logImp.appenderOpen(level, mode, cacheDir, logDir, nameprefix, cacheDays);
    }
  }

  public static appenderClose(): void {
    if (Log.logImp != null) {
      Log.logImp.appenderClose();
      for (let entry of this.sLogInstanceMap.entries()) {
        this.closeLogInstance(entry[0]);
      }
    }
  }

  public static appenderFlush(): void {
    if (Log.logImp != null) {
      Log.logImp.appenderFlush(0, false);
      for (let entry of this.sLogInstanceMap.entries()) {
        entry[1].appenderFlush();
      }
    }
  }

  public static appenderFlushSync(isSync: boolean): void {
    if (Log.logImp != null) {
      Log.logImp.appenderFlush(0, isSync);

    }
  }

  public static getLogLevel(): number {
    if (Log.logImp != null) {
      return Log.logImp.getLogLevel(0);
    }
    return Log.LEVEL_NONE;
  }

  public static setLevel(level: number, jni: boolean): void {
    Log.level = level;
    console.warn("new log level: " + level);

    if (jni) {
      console.error("no jni log level support");
    }
  }

  public static setConsoleLogOpen(isOpen: boolean): void {
    if (Log.logImp != null) {
      Log.logImp.setConsoleLogOpen(0, isOpen);
    }
  }

  /**
   * use f(tag, format, obj) instead
   *
   * @param tag
   * @param msg
   */
  public static f(tag: string, msg: string): void {
    this.fFunction(tag, msg, null);
  }

  /**
   * use e(tag, format, obj) instead
   *
   * @param tag
   * @param msg
   */
  public static e(tag: string, msg: string): void {
    this.eFunction(tag, msg, null);
  }

  /**
   * use w(tag, format, obj) instead
   *
   * @param tag
   * @param msg
   */
  public static w(tag: string, msg: string): void {
    this.wFunction(tag, msg, null);
  }

  /**
   * use i(tag, format, obj) instead
   *
   * @param tag
   * @param msg
   */
  public static i(tag: string, msg: string): void {
    this.iFunction(tag, msg, null);
  }

  /**
   * use d(tag, format, obj) instead
   *
   * @param tag
   * @param msg
   */
  public static d(tag: string, msg: string): void {
    this.dFunction(tag, msg, null);
  }

  /**
   * use v(tag, format, obj) instead
   *
   * @param tag
   * @param msg
   */
  public static v(tag: string, msg: string): void {
    this.vFunction(tag, msg, null);
  }

  public static fFunction(tag: string, format: string, obj: string): void {
    if (Log.logImp != null && Log.logImp.getLogLevel(0) <= this.LEVEL_FATAL) {
      let log: string = obj == null ? format : obj;
      Log.logImp.logF(0, tag, "", "", 0, 0, 0, 0, log);
    }
  }

  public static eFunction(tag: string, format: string, obj: string): void {
    if (Log.logImp != null && Log.logImp.getLogLevel(0) <= this.LEVEL_ERROR) {
      let log: string = obj == null ? format : obj;
      if (log == null) {
        log = "";
      }
      Log.logImp.logE(0, tag, "", "", 0, 0, 0, 0, log);
    }
  }

  public static wFunction(tag: string, format: string, obj: string): void {
    if (Log.logImp != null && Log.logImp.getLogLevel(0) <= this.LEVEL_WARNING) {
      let log: string = obj == null ? format : obj;
      if (log == null) {
        log = "";
      }
      Log.logImp.logW(0, tag, "", "", 0, 0, 0, 0, log);
    }
  }

  public static iFunction(tag: string, format: string, obj: string): void {
    if (Log.logImp != null && Log.logImp.getLogLevel(0) <= this.LEVEL_INFO) {
      let log: string = obj == null ? format : obj;
      if (log == null) {
        log = "";
      }
      Log.logImp.logI(0, tag, "", "", 0, 0, 0, 0, log);
    }
  }

  public static dFunction(tag: string, format: string, obj: string): void {
    if (Log.logImp != null && Log.logImp.getLogLevel(0) <= this.LEVEL_DEBUG) {
      let log: string = obj == null ? format : obj;
      if (log == null) {
        log = "";
      }
      Log.logImp.logD(0, tag, "", "", 0, 0, 0, 0, log);
    }
  }

  public static vFunction(tag: string, format: string, obj: string): void {
    if (Log.logImp != null && Log.logImp.getLogLevel(0) <= this.LEVEL_VERBOSE) {
      let log: string = obj == null ? format : obj;
      if (log == null) {
        log = "";
      }
      Log.logImp.logV(0, tag, "", "", 0, 0, 0, 0, log);
    }
  }

  public static printErrStackTrace(tag: string, tr: Error, format: string, obj: string): void {
    if (Log.logImp != null && Log.logImp.getLogLevel(0) <= this.LEVEL_ERROR) {
      let log: string = obj == null ? format : obj;
      if (log == null) {
        log = "";
      }
      log += "  " + tr.stack;
      Log.logImp.logE(0, tag, "", "", 0, process.pid, process.tid, process.uid, log);
    }
  }

  private static SYS_INFO: string;

  public static getDeviceInfo(): void {
    let sb: string = "";
    try {
      sb = "VERSION.RELEASE:[" + deviceInfo.osFullName;
      + "] VERSION.CODENAME:[" + deviceInfo.osReleaseType;
      + "] VERSION.INCREMENTAL:[" + deviceInfo.incrementalVersion;
      + "] DISPLAY:[" + deviceInfo.displayVersion;
      + "] HOST:[" + deviceInfo.buildHost;
      + "] MANUFACTURER:[" + deviceInfo.manufacture;
      + "] PRODUCT:[" + deviceInfo.productModel;
      + "] TYPE:[" + deviceInfo.buildType;
      + "] USER:[" + deviceInfo.buildUser;
    } catch(error) {
      console.info('Process Failed.getDeviceInfo Cause: ' + error);
    }

    Log.SYS_INFO = sb;
  }

  public static getSysInfo(): string {
    return Log.SYS_INFO;
  }

  private static sLogInstanceMap: Map<string, Log.LogInstance> = new Map<string, Log.LogInstance>();

  public static openLogInstance(level: number, mode: number, cacheDir: string, logDir: string, nameprefix: string, cacheDays: number): Log.LogInstance {
    if (Log.sLogInstanceMap.has(nameprefix)) {
      return Log.sLogInstanceMap.get(nameprefix);
    }
    let instance = new Log.LogInstance(level, mode, cacheDir, logDir, nameprefix, cacheDays);
    Log.sLogInstanceMap.set(nameprefix, instance);
    return instance;
  }

  public static closeLogInstance(prefix: string): void {
    if (null != Log.logImp) {
      if (Log.sLogInstanceMap.has(prefix)) {
        let logInstance = Log.sLogInstanceMap.get(prefix);
        logInstance.mLogInstancePtr = -1;
        Log.sLogInstanceMap.delete(prefix);
        Log.logImp.releaseXlogInstance(prefix);
      }
    }
  }

  public static getLogInstance(prefix: string): Log.LogInstance {
    if (Log.sLogInstanceMap.has(prefix)) {
      return Log.sLogInstanceMap.get(prefix);
    }
    return null;
  }

  public static LogInstance = class {
    public mLogInstancePtr: number = -1;

    public mPrefix: string = null;

    public constructor(level: number, mode: number, cacheDir: string, logDir: string, nameprefix: string, cacheDays: number) {
      if (Log.logImp != null) {
        this.mLogInstancePtr = Log.logImp.openLogInstance(level, mode, cacheDir, logDir, nameprefix, cacheDays);
        this.mPrefix = nameprefix;
      }
    }

    public f(tag: string, format: string, obj: string): void {
      if (Log.logImp != null && this.getLogLevel() <= Log.LEVEL_FATAL && this.mLogInstancePtr != -1) {
        let log: string = obj == null ? format : obj;
        Log.logImp.logF(this.mLogInstancePtr, tag, "", "", 0, 0, 0, 0, log);
      }
    }

    public e(tag: string, format: string, obj: string): void {
      if (Log.logImp != null && this.getLogLevel() <= Log.LEVEL_ERROR && this.mLogInstancePtr != -1) {
        let log: string = obj == null ? format : obj;
        if (log == null) {
          log = "";
        }
        Log.logImp.logE(this.mLogInstancePtr, tag, "", "", 0, 0, 0, 0, log);
      }
    }

    public w(tag: string, format: string, obj: string): void {
      if (Log.logImp != null && this.getLogLevel() <= Log.LEVEL_WARNING && this.mLogInstancePtr != -1) {
        let log: string = obj == null ? format : obj;
        if (log == null) {
          log = "";
        }
        Log.logImp.logW(this.mLogInstancePtr, tag, "", "", 0, 0, 0, 0, log);
      }
    }

    public i(tag: string, format: string, obj: string): void {
      if (Log.logImp != null && this.getLogLevel() <= Log.LEVEL_INFO && this.mLogInstancePtr != -1) {
        let log: string = obj == null ? format : obj;
        if (log == null) {
          log = "";
        }
        Log.logImp.logI(this.mLogInstancePtr, tag, "", "", 0, 0, 0, 0, log);
      }
    }

    public d(tag: string, format: string, obj: string): void {
      if (Log.logImp != null && this.getLogLevel() <= Log.LEVEL_DEBUG && this.mLogInstancePtr != -1) {
        let log: string = obj == null ? format : obj;
        if (log == null) {
          log = "";
        }
        Log.logImp.logD(this.mLogInstancePtr, tag, "", "", 0, 0, 0, 0, log);
      }
    }

    public v(tag: string, format: string, obj: string): void {
      if (Log.logImp != null && this.getLogLevel() <= Log.LEVEL_VERBOSE && this.mLogInstancePtr != -1) {
        let log: string = obj == null ? format : obj;
        if (log == null) {
          log = "";
        }
        Log.logImp.logV(this.mLogInstancePtr, tag, "", "", 0, 0, 0, 0, log);
      }
    }

    public printErrStackTrace(tag: string, tr: Error, format: string, obj: string): void {
      if (Log.logImp != null && this.getLogLevel() <= Log.LEVEL_ERROR && this.mLogInstancePtr != -1) {
        let log: string = obj == null ? format : obj;
        if (log == null) {
          log = "";
        }
        log += "  " + tr.stack;
        Log.logImp.logE(this.mLogInstancePtr, tag, "", "", process.pid, process.pid, process.tid, process.uid, log);
      }
    }

    public appenderFlush(): void {
      if (Log.logImp != null && this.mLogInstancePtr != -1) {
        Log.logImp.appenderFlush(this.mLogInstancePtr, false);
      }
    }

    public appenderFlushSync(): void {
      if (Log.logImp != null && this.mLogInstancePtr != -1) {
        Log.logImp.appenderFlush(this.mLogInstancePtr, true);
      }
    }

    public getLogLevel(): number {
      if (Log.logImp != null && this.mLogInstancePtr != -1) {
        return Log.logImp.getLogLevel(this.mLogInstancePtr);
      }
      return Log.LEVEL_NONE;
    }

    public setConsoleLogOpen(isOpen: boolean): void {
      if (null != Log.logImp && this.mLogInstancePtr != -1) {
        Log.logImp.setConsoleLogOpen(this.mLogInstancePtr, isOpen);
      }
    }
  }
}

namespace Log {
  export interface LogImp {
    logV(logInstancePtr: number, tag: string, filename: string, funcname: string, linuxTid: number,
         pid: number, tid: number, maintid: number, log: string): void;

    logI(logInstancePtr: number, tag: string, filename: string, funcname: string, linuxTid: number,
         pid: number, tid: number, maintid: number, log: string): void;

    logD(logInstancePtr: number, tag: string, filename: string, funcname: string, linuxTid: number,
         pid: number, tid: number, maintid: number, log: string): void;

    logW(logInstancePtr: number, tag: string, filename: string, funcname: string, linuxTid: number,
         pid: number, tid: number, maintid: number, log: string): void;

    logE(logInstancePtr: number, tag: string, filename: string, funcname: string, linuxTid: number,
         pid: number, tid: number, maintid: number, log: string): void;

    logF(logInstancePtr: number, tag: string, filename: string, funcname: string, linuxTid: number,
         pid: number, tid: number, maintid: number, log: string): void;

    getLogLevel(logInstancePtr: number): number;

    setAppenderMode(logInstancePtr: number, mode: number): void;

    openLogInstance(level: number, mode: number, cacheDir: string, logDir: string,
                    nameprefix: string, cacheDays: number): number;

    getXlogInstance(nameprefix: string): number;

    releaseXlogInstance(nameprefix: string): void;

    appenderOpen(level: number, mode: number, cacheDir: string, logDir: string,
                 nameprefix: string, cacheDays: number): void;

    appenderClose(): void;

    appenderFlush(logInstancePtr: number, isSync: boolean): void;

    setConsoleLogOpen(logInstancePtr: number, isOpen: boolean): void;

    setMaxFileSize(logInstancePtr: number, aliveSeconds: number): void;

    setMaxAliveTime(logInstancePtr: number, aliveSeconds: number): void;
  }

  export type LogInstance = typeof Log.LogInstance.prototype;
}

export default Log;